/**
 * 2017年12月7日
 */
package exp.algorithm.gbdt;

import java.util.ArrayList;
import java.util.Random;

import timeseriesweka.classifiers.TSF;
import timeseriesweka.classifiers.TSF.FeatureSet;
import utilities.ClassifierResults;
import utilities.ClassifierTools;
import utilities.CrossValidator;
import weka.classifiers.AbstractClassifier;
import weka.classifiers.trees.RandomForest;
import weka.classifiers.trees.RandomTree;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.TechnicalInformation;

/**
 * @author Alex
 *
 */
public class HeterogeneousTSF extends AbstractClassifier {
	boolean setSeed = false;
	int seed = 0;
	RandomTree[] trees;
	int numTrees = 500;
	int numFeatures;
	int[][][] intervals;
	Random rand;
	Instances testHolder;
	/*
	 * There is no benefit from internally doing the CV for this classifier, so
	 * this is just for debugging really. Somewhat tidier
	 */
	boolean trainCV = false;
	/*
	 * If nonTrain results are overwritten with each call to buildClassifier
	 * File opened on this path.
	 */
	String trainCVPath = "";

	public HeterogeneousTSF() {
		rand = new Random();
	}

	public HeterogeneousTSF(int s) {
		rand = new Random();
		seed = s;
		rand.setSeed(seed);
		setSeed = true;
	}

	public void setSeed(int s) {
		this.setSeed = true;
		seed = s;
		rand = new Random();
		rand.setSeed(seed);
	}


	public void setNumTrees(int t) {
		numTrees = t;
	}

	// <editor-fold defaultstate="collapsed" desc="results reported in Info
	// Sciences paper">
	static double[] reportedResults = { 0.2659, 0.2302, 0.2333, 0.0256, 0.2537, 0.0391, 0.0357, 0.2897, 0.2, 0.2436,
			0.049, 0.08, 0.0557, 0.2325, 0.0227, 0.101, 0.1543, 0.0467, 0.552, 0.6818, 0.0301, 0.1803, 0.2603, 0.0448,
			0.2237, 0.119, 0.0987, 0.0865, 0.0667, 0.4339, 0.233, 0.1868, 0.0357, 0.1056, 0.1116, 0.0267, 0.02, 0.1177,
			0.0543, 0.2102, 0.2876, 0.2624, 0.0054, 0.3793, 0.1513 };
	// </editor-fold>

	// <editor-fold defaultstate="collapsed" desc="problems used in Info
	// Sciences paper">
	static String[] problems = { "FiftyWords", "Adiac", "Beef", "CBF", "ChlorineConcentration", "CinCECGtorso",
			"Coffee", "CricketX", "CricketY", "CricketZ", "DiatomSizeReduction", "ECG", "ECGFiveDays", "FaceAll",
			"FaceFour", "FacesUCR", "Fish", "GunPoint", "Haptics", "InlineSkate", "ItalyPowerDemand", "Lightning2",
			"Lightning7", "Mallat", "MedicalImages", "MoteStrain", "NonInvasiveFetalECGThorax1",
			"NonInvasiveFetalECGThorax2", "OliveOil", "OSULeaf", "SonyAIBORobotSurface1", "SonyAIBORobot Surface2",
			"StarLightCurves", "SwedishLeaf", "Symbols", "Synthetic Control", "Trace", "TwoLeadECG", "TwoPatterns",
			"UWaveGestureLibraryX", "UWaveGestureLibraryY", "UWaveGestureLibraryZ", "Wafer", "WordsSynonyms", "Yoga" };
	// </editor-fold>

	public TechnicalInformation getTechnicalInformation() {
		TechnicalInformation result;
		result = new TechnicalInformation(TechnicalInformation.Type.ARTICLE);
		result.setValue(TechnicalInformation.Field.AUTHOR, "H. Deng, G. Runger, E. Tuv and M. Vladimir");
		result.setValue(TechnicalInformation.Field.YEAR, "2013");
		result.setValue(TechnicalInformation.Field.TITLE,
				"A time series forest for classification and feature extraction");
		result.setValue(TechnicalInformation.Field.JOURNAL, "Information Sciences");
		result.setValue(TechnicalInformation.Field.VOLUME, "239");
		result.setValue(TechnicalInformation.Field.PAGES, "142-153");

		return result;
	}

	@Override
	public void buildClassifier(Instances data) throws Exception {
		numFeatures = (int) Math.sqrt(data.numAttributes() - 1);
		intervals = new int[numTrees][][];
		trees = new RandomTree[numTrees];
		ArrayList<Attribute> atts = new ArrayList<Attribute>();
		String name;
		for (int j = 0; j < numFeatures * 3; j++) {
			name = "F" + j;
			atts.add(new Attribute(name));
		}
		Attribute target = data.attribute(data.classIndex());
		ArrayList<String> vals = new ArrayList<String>(target.numValues());
		for (int j = 0; j < target.numValues(); j++)
			vals.add(target.value(j));
		atts.add(new Attribute(data.attribute(data.classIndex()).name(), vals));
		Instances result = new Instances("Tree", atts, data.numInstances());
		result.setClassIndex(result.numAttributes() - 1);
		for (int i = 0; i < data.numInstances(); i++) {
			DenseInstance in = new DenseInstance(result.numAttributes());
			in.setValue(result.numAttributes() - 1, data.instance(i).classValue());
			result.add(in);
		}
		testHolder = new Instances(result, 0);
		DenseInstance in = new DenseInstance(result.numAttributes());
		testHolder.add(in);
		for (int i = 0; i < numTrees; i++) {
			intervals[i] = new int[numFeatures][2]; // Start and end
			for (int j = 0; j < numFeatures; j++) {
				intervals[i][j][0] = rand.nextInt(data.numAttributes() - 1); // Start
				int length = rand.nextInt(data.numAttributes() - 1 - intervals[i][j][0]);// Min
				intervals[i][j][1] = intervals[i][j][0] + length;
			}
			for (int j = 0; j < numFeatures; j++) {
				for (int k = 0; k < data.numInstances(); k++) {
					double[] series = data.instance(k).toDoubleArray();
					FeatureSet f = new FeatureSet();
					f.setFeatures(series, intervals[i][j][0], intervals[i][j][1]);
					result.instance(k).setValue(j * 3, f.mean);
					result.instance(k).setValue(j * 3 + 1, f.stDev);
					result.instance(k).setValue(j * 3 + 2, f.slope);
				}
			}
			trees[i] = new RandomTree();
			trees[i].setKValue(numFeatures);
			trees[i].buildClassifier(result);
		}

	}

	@Override
	public double[] distributionForInstance(Instance ins) throws Exception {
		double[] d = new double[ins.numClasses()];
		;
		int[] votes = new int[ins.numClasses()];
		// Build instance
		double[] series = ins.toDoubleArray();
		for (int i = 0; i < trees.length; i++) {
			for (int j = 0; j < numFeatures; j++) {
				// extract the interval
				FeatureSet f = new FeatureSet();
				f.setFeatures(series, intervals[i][j][0], intervals[i][j][1]);
				testHolder.instance(0).setValue(j * 3, f.mean);
				testHolder.instance(0).setValue(j * 3 + 1, f.stDev);
				testHolder.instance(0).setValue(j * 3 + 2, f.slope);
			}
			int c = (int) trees[i].classifyInstance(testHolder.instance(0));
			votes[c]++;
		}
		double sum = 0;
		for (int x : votes)
			sum += x;
		for (int i = 0; i < d.length; i++)
			d[i] = votes[i] / sum;
		return d;
	}

	@Override
	public double classifyInstance(Instance ins) throws Exception {
		int[] votes = new int[ins.numClasses()];
		// Build instance
		double[] series = ins.toDoubleArray();
		for (int i = 0; i < trees.length; i++) {
			for (int j = 0; j < numFeatures; j++) {
				// extract the interval
				FeatureSet f = new FeatureSet();
				f.setFeatures(series, intervals[i][j][0], intervals[i][j][1]);
				testHolder.instance(0).setValue(j * 3, f.mean);
				testHolder.instance(0).setValue(j * 3 + 1, f.stDev);
				testHolder.instance(0).setValue(j * 3 + 2, f.slope);
			}
			int c = (int) trees[i].classifyInstance(testHolder.instance(0));
			votes[c]++;
		}
		// Return majority vote
		int maxVote = 0;
		for (int i = 1; i < votes.length; i++)
			if (votes[i] > votes[maxVote])
				maxVote = i;
		return maxVote;
	}

	public static class FeatureSet {
		double mean;
		double stDev;
		double slope;
		RandomForest r;

		public void setFeatures(double[] data, int start, int end) {
			double sumX = 0, sumYY = 0;
			double sumY = 0, sumXY = 0, sumXX = 0;
			int length = end - start + 1;
			for (int i = start; i <= end; i++) {
				sumY += data[i];
				sumYY += data[i] * data[i];
				sumX += (i - start);
				sumXX += (i - start) * (i - start);
				sumXY += data[i] * (i - start);
			}
			mean = sumY / length;
			stDev = sumYY - (sumY * sumY) / length;
			slope = (sumXY - (sumX * sumY) / length);
			if (sumXX - (sumX * sumX) / length != 0)
				slope /= sumXX - (sumX * sumX) / length;
			else
				slope = 0;
			stDev /= length;
			if (stDev == 0) // Flat line
				slope = 0;
			// else
			// stDev=Math.sqrt(stDev);
			if (slope == 0)
				stDev = 0;
		}

		public void setFeatures(double[] data) {
			setFeatures(data, 0, data.length - 1);
		}

		@Override
		public String toString() {
			return "mean=" + mean + " stdev = " + stDev + " slope =" + slope;
		}
	}

}
